package besta.moon.sql.expression.function.system;

import java.util.Arrays;

import besta.moon.engine.table.Column;
import besta.moon.sql.Expression;
import besta.moon.MoonException;
import besta.moon.MoonCode;
import besta.moon.sql.datatype.DateTime;
import besta.moon.sql.datatype.Money;
import besta.moon.sql.datatype.MutableNumeric;
import besta.moon.sql.expression.ExpressionValue;
import besta.moon.sql.expression.function.ExpressionFunction;
import besta.moon.sql.parser.SQLTokenizer;
import besta.moon.util.MoonUtils;

public class FunctionConvert extends ExpressionFunction {

    final private Column datatype;

    public FunctionConvert(Column datatype, Expression value, Expression style) {
        super();
        this.datatype = datatype;
        Expression[] params = (style == null) ? new Expression[]{value} : new Expression[]{value, style};
        setParams(params);
    }

    @Override
    public int getFunction() {
        return SQLTokenizer.CONVERT;
    }

    @Override
    public boolean isNull() throws Exception {
        return param1.isNull();
    }

    @Override
    public boolean getBoolean() throws Exception {
        return ExpressionValue.getBoolean(getObject(), getDataType());
    }

    @Override
    public int getInt() throws Exception {
        return ExpressionValue.getInt(getObject(), getDataType());
    }

    @Override
    public long getLong() throws Exception {
        return ExpressionValue.getLong(getObject(), getDataType());
    }

    @Override
    public float getFloat() throws Exception {
        return ExpressionValue.getFloat(getObject(), getDataType());
    }

    @Override
    public double getDouble() throws Exception {
        return ExpressionValue.getDouble(getObject(), getDataType());
    }

    @Override
    public long getMoney() throws Exception {
        return ExpressionValue.getMoney(getObject(), getDataType());
    }

    @Override
    public MutableNumeric getNumeric() throws Exception {
        return ExpressionValue.getNumeric(getObject(), getDataType());
    }

    @Override
    public String getString() throws Exception {
        Object obj = getObject();
        if (obj == null) {
            return null;
        }
        switch (datatype.getDataType()) {
            case SQLTokenizer.BIT:
                return ((Boolean) obj) ? "1" : "0";
            case SQLTokenizer.BINARY:
            case SQLTokenizer.VARBINARY:
            case SQLTokenizer.LONGVARBINARY:
                return new String((byte[]) obj);
        }
        return obj.toString();
    }

    @Override
    public Object getObject() throws Exception {
        if (param1.isNull()) {
            return null;
        }
        final int dataType = getDataType();
        switch (dataType) {
            case SQLTokenizer.LONGVARCHAR:
                return convertToString();
            case SQLTokenizer.VARCHAR: {
                String str = convertToString();
                int length = datatype.getDisplaySize();
                if (length > str.length()) {
                    return str;
                }
                return str.substring(0, length);
            }
            case SQLTokenizer.CHAR: {
                String str = convertToString();
                int length = datatype.getDisplaySize();
                if (length > str.length()) {
                    char[] buffer = new char[length - str.length()];
                    Arrays.fill(buffer, ' ');
                    return str + new String(buffer);
                }
                return str.substring(0, length);
            }
            case SQLTokenizer.LONGVARBINARY:
                return param1.getBytes();
            case SQLTokenizer.VARBINARY: {
                byte[] bytes = param1.getBytes();
                int length = datatype.getPrecision();
                if (length < bytes.length) {
                    byte[] buffer = new byte[length];
                    System.arraycopy(bytes, 0, buffer, 0, Math.min(bytes.length, length));
                    return buffer;
                }
                return bytes;

            }
            case SQLTokenizer.BINARY: {
                byte[] bytes = param1.getBytes();
                int length = datatype.getPrecision();
                if (length != bytes.length) {
                    byte[] buffer = new byte[length];
                    System.arraycopy(bytes, 0, buffer, 0, Math.min(bytes.length, length));
                    return buffer;
                }
                return bytes;

            }
            case SQLTokenizer.BOOLEAN:
            case SQLTokenizer.BIT:
                return param1.getBoolean() ? Boolean.TRUE : Boolean.FALSE;
            case SQLTokenizer.TINYINT:
                return MoonUtils.getInteger(param1.getInt() & 0xFF);
            case SQLTokenizer.SMALLINT:
                return MoonUtils.getInteger((short) param1.getInt());
            case SQLTokenizer.INT:
                return MoonUtils.getInteger(param1.getInt());
            case SQLTokenizer.BIGINT:
                return param1.getLong();
            case SQLTokenizer.REAL:
                return param1.getFloat();
            case SQLTokenizer.FLOAT:
            case SQLTokenizer.DOUBLE:
                return param1.getDouble();
            case SQLTokenizer.DATE:
            case SQLTokenizer.TIME:
            case SQLTokenizer.TIMESTAMP:
            case SQLTokenizer.SMALLDATETIME:
                return new DateTime(getDateTimeLong(), dataType);
            case SQLTokenizer.NUMERIC:
            case SQLTokenizer.DECIMAL:
                MutableNumeric num = param1.getNumeric();
                if (num != null && (dataType == SQLTokenizer.NUMERIC || dataType == SQLTokenizer.DECIMAL)) {
                    num.setScale(getScale());
                }
                return num;
            case SQLTokenizer.MONEY:
            case SQLTokenizer.SMALLMONEY:
                return Money.createFromUnscaledValue(param1.getMoney());
            case SQLTokenizer.UNIQUEIDENTIFIER:
                switch (param1.getDataType()) {
                    case SQLTokenizer.VARCHAR:
                    case SQLTokenizer.CHAR:
                    case SQLTokenizer.LONGVARCHAR:
                    case SQLTokenizer.CLOB:
                        return MoonUtils.bytes2unique(MoonUtils.unique2bytes(param1.getString()), 0);
                }
                return MoonUtils.bytes2unique(param1.getBytes(), 0);
        }
        Object[] param = {SQLTokenizer.getKeyWord(dataType)};
        throw MoonException.create(MoonCode.UNSUPPORTED_TYPE_CONV, param);
    }

    private String convertToString() throws Exception {
        if (param2 != null) {
            int type = param1.getDataType();
            switch (type) {
                case SQLTokenizer.SMALLDATETIME:
                    type = SQLTokenizer.TIMESTAMP;
                case SQLTokenizer.TIMESTAMP:
                case SQLTokenizer.DATE:
                case SQLTokenizer.TIME:
                    return new DateTime(param1.getLong(), type).toString(param2.getInt());
                default:
                    return param1.getString();
            }
        } else {
            return param1.getString();
        }
    }

    private long getDateTimeLong() throws Exception {
        switch (param1.getDataType()) {
            case SQLTokenizer.LONGVARCHAR:
            case SQLTokenizer.VARCHAR:
            case SQLTokenizer.CHAR:
                return DateTime.parse(param1.getString());
        }
        return param1.getLong();
    }

    @Override
    public final int getDataType() {
        return datatype.getDataType();
    }

    @Override
    public final int getPrecision() {
        final int dataType = getDataType();
        switch (dataType) {
            case SQLTokenizer.VARCHAR:
            case SQLTokenizer.VARBINARY:
            case SQLTokenizer.BINARY:
            case SQLTokenizer.CHAR:
            case SQLTokenizer.NUMERIC:
            case SQLTokenizer.DECIMAL:
                return datatype.getPrecision();
            default:
                return super.getPrecision();
        }
    }

    @Override
    public final int getScale() {
        return datatype.getScale();
    }

}
